class VoronoiTreemapChart {
  constructor(selector, config = {}) {
    this.svg = d3.select(selector);
    this.width = config.width || 900;
    this.height = config.height || 700;
    this.padding = config.padding || 20;
    this.clip = [
      [this.padding, this.padding],
      [this.width - this.padding, this.padding],
      [this.width - this.padding, this.height - this.padding],
      [this.padding, this.height - this.padding]
    ];
    this.moduleFillOpacity = config.moduleFillOpacity ?? 0.12;
    this.moduleStrokeColor = config.moduleStrokeColor ?? "#75c755ff";
    this.moduleStrokeWidth = config.moduleStrokeWidth ?? 5;
    this.moduleHaloWidth = config.moduleHaloWidth ?? 8; // 白色间隔效果
  }

  render(data) {
    if (!data) return;
    const root = d3.hierarchy(data).sum(d => d.weight || 1);
    const voronoiTreemap = d3.voronoiTreemap().clip(this.clip);
    voronoiTreemap(root);

    const svg = this.svg.attr("width", this.width).attr("height", this.height);
    svg.selectAll("*").remove();

    // 一级模块（children）与叶子
    const modules = root.children || [];
    const leaves = root.leaves() || [];

    // Tooltip (keep one)
    const tooltip = d3.select("body").selectAll(".vt-tooltip").data([0]);
    const tooltipEnter = tooltip.enter().append("div").attr("class", "vt-tooltip");
    tooltipEnter.merge(tooltip)
      .style("position", "absolute")
      .style("background", "rgba(255,255,255,0.95)")
      .style("border", "1px solid #bbb")
      .style("padding", "6px 10px")
      .style("border-radius", "6px")
      .style("font-size", "12px")
      .style("box-shadow", "0 2px 6px rgba(0,0,0,0.18)")
      .style("visibility", "hidden");

    const tooltipNode = d3.select("body").select(".vt-tooltip");

    // --- 1) 先绘制叶子（公司小块）在底层（有白色细边）---
    svg.append("g").attr("class", "leaves")
      .selectAll("path.leaf")
      .data(leaves)
      .enter()
      .append("path")
      .attr("class", "leaf")
      .attr("d", d => "M" + d.polygon.join("L") + "Z")
      .attr("fill", d => d.data.color || this.pickColor(d))
      .attr("stroke", "#fff")
      .attr("stroke-width", 1.2)
      .on("mouseover", function (event, d) {
        d3.select(this).raise().attr("stroke", "#000").attr("stroke-width", 2);
        tooltipNode.style("visibility", "visible")
          .html(`<b>${d.data.name}</b><br>模块：${d.parent.data.name}<br>专利数：${d.data.weight || "-"}`);
      })
      .on("mousemove", function (event) {
        tooltipNode.style("top", (event.pageY + 12) + "px")
                   .style("left", (event.pageX + 12) + "px");
      })
      .on("mouseout", function () {
        d3.select(this).attr("stroke", "#fff").attr("stroke-width", 1.2);
        tooltipNode.style("visibility", "hidden");
      });

    // --- 2) 绘制模块的“白色空隙”（halo）：在模块边上放一条较宽的白色描边，营造模块间分隔感）---
    const moduleGroup = svg.append("g").attr("class", "modules");

    // Halo (white thick stroke)
    moduleGroup.selectAll("path.module-halo")
      .data(modules)
      .enter()
      .append("path")
      .attr("class", "module-halo")
      .attr("d", d => "M" + d.polygon.join("L") + "Z")
      .attr("fill", "none")
      .attr("stroke", "#ffffff")
      .attr("stroke-width", this.moduleHaloWidth)
      .attr("stroke-linejoin", "round")
      .attr("pointer-events", "none") // 不阻碍鼠标事件
      .attr("opacity", 1);

    // --- 3) 绘制模块本体（浅色填充 + 边框）---
    moduleGroup.selectAll("path.module")
      .data(modules)
      .enter()
      .append("path")
      .attr("class", "module")
      .attr("d", d => "M" + d.polygon.join("L") + "Z")
      .attr("fill", (d, i) => this.moduleColor(i))
      .attr("fill-opacity", this.moduleFillOpacity)
      .attr("stroke", this.moduleStrokeColor)
      .attr("stroke-width", this.moduleStrokeWidth)
      .attr("stroke-linejoin", "round")
      .attr("pointer-events", "none");

    // --- 4) 模块标签：使用多边形质心，如果质心在凹处偏离较大则仍可接受 ---
    const labelGroup = svg.append("g").attr("class", "module-labels");
    labelGroup.selectAll("text.module-label")
      .data(modules)
      .enter()
      .append("text")
      .attr("class", "module-label")
      .attr("x", d => {
        // 优先使用 polygon.site 如果存在，否则 polygon centroid
        if (d.polygon && d.polygon.site) return d.polygon.site.x;
        return d3.polygonCentroid(d.polygon)[0];
      })
      .attr("y", d => {
        if (d.polygon && d.polygon.site) return d.polygon.site.y;
        return d3.polygonCentroid(d.polygon)[1];
      })
      .attr("text-anchor", "middle")
      .style("fill", "#0d0d0d")
      .style("font-weight", "700")
      .style("pointer-events", "none")
      .style("dominant-baseline", "middle")
      .style("font-size", d => this.moduleFontSize(d))
      .text(d => d.data.name);

    // --- 5) 可选：在模块标签上加描边 / 半透明背景以提升可读性（这里给每个标签添加一个白色轻描边） ---
    // 为了简单、兼容性高，我们在文本后面画一个半透明白色圆（如果需求更复杂可改为文本背景矩形）
    labelGroup.selectAll("circle.label-bg")
      .data(modules)
      .enter()
      .append("circle")
      .attr("class", "label-bg")
      .attr("cx", d => (d.polygon && d.polygon.site) ? d.polygon.site.x : d3.polygonCentroid(d.polygon)[0])
      .attr("cy", d => (d.polygon && d.polygon.site) ? d.polygon.site.y : d3.polygonCentroid(d.polygon)[1])
      .attr("r", d => {
        const area = Math.abs(d3.polygonArea(d.polygon));
        return Math.max(24, Math.min(60, Math.sqrt(area) / 6));
      })
      .attr("fill", "white")
      .attr("fill-opacity", 0.7)
      .attr("stroke", "none")
      .lower(); // 放到标签下方，避免遮挡

    // ensure the circle backgrounds are behind text
    svg.selectAll("g.module-labels").each(function() {
      // move circles to back of label group
      const g = d3.select(this);
      g.selectAll("circle.label-bg").lower();
      g.selectAll("text.module-label").raise();
    });

        // --- 6) 动态生成颜色图例（legend）---
    // 从叶子节点提取公司及颜色映射
    const companyColorMap = new Map();
    leaves.forEach(d => {
      const company = d.data.name;
      const color = d.data.color || this.pickColor(d);
      if (!companyColorMap.has(company)) {
        companyColorMap.set(company, color);
      }
    });

    // 生成图例数据
    const legendData = Array.from(companyColorMap.entries()).map(([name, color]) => ({ name, color }));

    // 绘制图例区域（默认放在右下角）
    const legendWidth = 260;
    const legendPadding = 10;
    const legendGroup = svg.append("g")
      .attr("class", "legend")
      .attr("transform", `translate(${this.width - legendWidth - 20}, ${this.height - legendData.length * 24 - 40})`);

    // 图例标题
    legendGroup.append("text")
      .text("颜色图例（公司对应关系）")
      .attr("x", 0)
      .attr("y", -10)
      .attr("font-size", "14px")
      .attr("font-weight", "600");

    // 每个图例项
    const legendItem = legendGroup.selectAll(".legend-item")
      .data(legendData)
      .enter()
      .append("g")
      .attr("class", "legend-item")
      .attr("transform", (d, i) => `translate(${legendPadding}, ${i * 24})`);

    // 色块（圆角矩形）
    legendItem.append("rect")
      .attr("x", 0)
      .attr("y", 0)
      .attr("width", 20)
      .attr("height", 20)
      .attr("fill", d => d.color)
      .attr("stroke", "#999")
      .attr("stroke-width", 0.8)
      .attr("rx", 4)
      .attr("ry", 4)
      .style("filter", "drop-shadow(0 1px 2px rgba(0,0,0,0.15))");

    // 文字说明
    legendItem.append("text")
      .attr("x", 28)
      .attr("y", 14)
      .text(d => d.name)
      .attr("font-size", "13px")
      .attr("fill", "#222");

  }

  // 颜色选择：模块色板（循环）
  moduleColor(i) {
    const palette = ["#cfeef0", "#e6f6de", "#f9f0d9", "#e8f0ff", "#e7f9ef", "#fff0e6"];
    return palette[i % palette.length];
  }

  // 叶子默认颜色（如果 data.color 未提供）
  pickColor(d) {
    const fallback = ["#6cc070", "#609dd2", "#fa7f6f", "#f2c94c", "#8ec5d6", "#b6e3a9"];
    // try to pick by parent index for visual grouping
    const parentIndex = d.parent ? (d.parent.data && d.parent.data.__index || 0) : 0;
    return d.data.color || fallback[(parentIndex + (d.data && (d.data.name || "").length)) % fallback.length];
  }

  // 自动计算模块字体大小（基于面积）
  moduleFontSize(d) {
    try {
      const area = Math.abs(d3.polygonArea(d.polygon));
      const size = Math.max(14, Math.min(34, Math.sqrt(area) / 8));
      return Math.round(size) + "px";
    } catch (e) {
      return "16px";
    }
  }

  
}
